package service

import (
	"context"
	"gitee.com/chicken-c/go-learn/webook/internal/domain"
	"gitee.com/chicken-c/go-learn/webook/internal/repostiory"
	"github.com/ecodeclub/ekit/queue"
	"github.com/ecodeclub/ekit/slice"
	"math"
	"time"
)

type RankingService interface {
	TopN(ctx context.Context) error
}

type BatchRankingService struct {
	intrSvc InterActiveService
	artSvc  ArticleService
	repo    repostiory.RankingRepostiory

	BatchSize int64
	scoreFunc func(likeCnt int64, utime time.Time) float64
	//TopN的N
	n int
}

func NewBatchRankingService(intrSvc InterActiveService, artSvc ArticleService, repo repostiory.RankingRepostiory) RankingService {
	return &BatchRankingService{
		intrSvc:   intrSvc,
		artSvc:    artSvc,
		repo:      repo,
		BatchSize: 3,

		n: 100,
		scoreFunc: func(likeCnt int64, utime time.Time) float64 {
			Duration := time.Now().Sub(utime).Seconds()
			return float64(likeCnt-1) / math.Pow(Duration+2, 1.5)
		},
	}
}

func (b *BatchRankingService) TopN(ctx context.Context) error {
	arts, err := b.topN(ctx)
	if err != nil {
		return err
	}

	//存入缓存
	err = b.repo.ReplaceTopN(ctx, arts)
	return err
}

func (b *BatchRankingService) topN(ctx context.Context) ([]domain.Article, error) {
	var offset int64 = 0
	start := time.Now()
	ddl := start.Add(-7 * 24 * time.Hour)

	type Score struct {
		score float64
		art   domain.Article
	}

	//建立小根堆
	topN := queue.NewPriorityQueue(b.n, func(s1 Score, s2 Score) int {
		if s1.score > s2.score {
			return 1
		} else if s1.score == s2.score {
			return 0
		} else {
			return -1
		}
	})

	for {
		//取文章
		arts, err := b.artSvc.ListPub(ctx, start, offset, b.BatchSize)
		if err != nil {
			return nil, err
		}

		//获取ids
		ids := slice.Map(arts, func(idx int, art domain.Article) int64 {
			return art.Id
		})

		intrMap, err := b.intrSvc.GetByIds(ctx, "article", ids)
		if err != nil {
			return nil, err
		}

		for _, art := range arts {
			intr, ok := intrMap[art.Id]
			if !ok {
				continue
			}

			score := b.scoreFunc(intr.LikeCnt, art.Utime)
			ele := Score{
				score: score,
				art:   art,
			}
			err = topN.Enqueue(ele)
			if err == queue.ErrOutOfCapacity {
				//堆满了
				//我们取出最小的
				min, _ := topN.Dequeue()
				if score < min.score {
					_ = topN.Enqueue(min)
				} else {
					_ = topN.Enqueue(ele)
				}
			}
		}

		offset = offset + int64(len(arts))
		//没有下一批了
		if int64(len(arts)) < b.BatchSize ||
			//一个小优化，这一批次的最后一条数据的时间如果大于7天就不需要了
			arts[len(arts)-1].Utime.Before(ddl) {
			break
		}
	}

	//这边topN里面就是最终结果了
	res := make([]domain.Article, topN.Len())
	for i := topN.Len() - 1; i >= 0; i-- {
		tmp, _ := topN.Dequeue()
		res[i] = tmp.art
	}

	return res, nil
}
